package com.aneliya.io.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import org.w3c.dom.Document;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSException;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import socket.io.InputStreamReporter;
import socket.io.WrappedInputStream;
import socket.io.WrappedOutputStream;

import com.aneliya.io.IStreamStrategy;
import com.aneliya.model.Message;

public class XMLStreamStrategy implements IStreamStrategy {

	private XMLMessageFactory xmlMessageFactory;
	
	private LSSerializer serializer;
	private LSOutput lsoutput;
	
	private InputSource inputSource;
	
	/**
	 * Instead of a SAX parser we can use the DOM-based XMLMessageParser, which allows
	 * a more structured, easier and straigthforward way to process the incoming message xml. 
	 */
	private XMLReader xmlReader;
	private XMLMessageContentHandler contentHandler;
	
	private InputStream inputStream;
	public void setInputStream(InputStream inputStream) {
		this.inputStream = inputStream;
	}

	private OutputStream outputStream;
	public void setOutputStream(OutputStream outputStream) {
		this.outputStream = outputStream;
	}
	
	public Message readMessage() {
		try {
			/**
			 *  Very often, applications need to transmit XML documents over the network using 
			 *  a socket stream. However, XML is not designed to make this possible because 
			 *  XML documents do not contain an explicit end-of-document terminal. Therefore, 
			 *  the stream must end (i.e. the socket must close) in order for the parser to 
			 *  finish parsing the complete XML document.
			 *  Since creating socket streams is expensive the application needs to re-use the 
			 *  same stream but XML doesn't define an end-of-document. Therefore, another 
			 *  solution must be found.
			 *  
			 *  This solution introduces wraps on both the input and output stream on both 
			 *  ends of the socket. The stream wrappers introduce a protocol that allows 
			 *  arbitrary length data to be sent as separate, localized input streams. While 
			 *  the socket stream remains open, a separate input stream is created to "wrap" 
			 *  an incoming document and make it appear as if it were a standalone input stream.
			 */
			WrappedInputStream wrappedInputStream = new WrappedInputStream(inputStream);
			InputStream inputStreamReporter = new InputStreamReporter(wrappedInputStream);
			inputSource = new InputSource(inputStreamReporter);
			xmlReader.parse(inputSource);
			wrappedInputStream.close();
			
			Message message = contentHandler.getMessage();
			return message;
		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (SAXException ex) {
			ex.printStackTrace();
		}
		return null;
	}
	
	public void writeMessage(Message message) {
		try {
			Document xmlMessage = xmlMessageFactory.createXMLMessage(message);
			
			/**
			 *  Very often, applications need to transmit XML documents over the network using 
			 *  a socket stream. However, XML is not designed to make this possible because 
			 *  XML documents do not contain an explicit end-of-document terminal. Therefore, 
			 *  the stream must end (i.e. the socket must close) in order for the parser to 
			 *  finish parsing the complete XML document.
			 *  Since creating socket streams is expensive the application needs to re-use the 
			 *  same stream but XML doesn't define an end-of-document. Therefore, another 
			 *  solution must be found.
			 *  
			 *  This solution introduces wraps on both the input and output stream on both 
			 *  ends of the socket. The stream wrappers introduce a protocol that allows 
			 *  arbitrary length data to be sent as separate, localized input streams. While 
			 *  the socket stream remains open, a separate input stream is created to "wrap" 
			 *  an incoming document and make it appear as if it were a standalone input stream.
			 */
			WrappedOutputStream wrappedOutputStream = new WrappedOutputStream(outputStream);
			lsoutput.setByteStream(wrappedOutputStream);
			serializer.write(xmlMessage, lsoutput);
			wrappedOutputStream.close();
		} catch (LSException ex) {
			ex.printStackTrace();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	
	public void initialize() {
		try {
			xmlMessageFactory = new XMLMessageFactory();
			
			DOMImplementationRegistry domImplementationRegistry = DOMImplementationRegistry.newInstance();
			DOMImplementationLS loadSaveImplementation = 
				(DOMImplementationLS) domImplementationRegistry.getDOMImplementation("LS");
			
			serializer = loadSaveImplementation.createLSSerializer();
			lsoutput = loadSaveImplementation.createLSOutput();
			
			xmlReader = XMLReaderFactory.createXMLReader(
					"org.apache.xerces.parsers.SAXParser");
			contentHandler = new XMLMessageContentHandler();
			xmlReader.setContentHandler(contentHandler);
		} catch (ClassCastException ex) {
			ex.printStackTrace();
		} catch (ClassNotFoundException ex) {
			ex.printStackTrace();
		} catch (InstantiationException ex) {
			ex.printStackTrace();
		} catch (IllegalAccessException ex) {
			ex.printStackTrace();
		} catch (SAXException ex) {
			ex.printStackTrace();
		}
	}
	
	public XMLStreamStrategy(Socket socket) {
		try {
			setInputStream(socket.getInputStream());
			setOutputStream(socket.getOutputStream());
			
			initialize();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	
	public XMLStreamStrategy() {
		// Default constructor
	}

}
